package com.dsz.usercenter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dsz.usercenter.common.BaseResponse;
import com.dsz.usercenter.common.ErrorCode;
import com.dsz.usercenter.common.ResultUtil;
import com.dsz.usercenter.exception.BusinessException;
import com.dsz.usercenter.mapper.UserMapper;
import com.dsz.usercenter.model.domain.User;
import com.dsz.usercenter.model.vo.UserVO;
import com.dsz.usercenter.service.UserService;
import com.dsz.usercenter.utils.AlgorithmUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import javafx.util.Pair;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.dsz.usercenter.constant.UserConstant.ADMIN_ROLE;
import static com.dsz.usercenter.constant.UserConstant.USER_LOGIN_STATE;

/**
 * @description 针对表【user(用户)】的数据库操作Service实现
 * @createDate 2024-01-06 21:41:28
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
//    private ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();这样写会npe
    /**
     * 盐值，混淆密码
     */
    private static final String SALT = "yupi";

    //伙伴匹配系统

    /**
     * 获取主页推荐用户
     * @return 用户列表
     */
    @Override
    public List<User> getRecommendUsers(int pageSize, int pageNum, HttpServletRequest request) {
        ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
        //先从缓存中取
        User user = this.getCurrentUser(request);
        String key = String.format("yupao:user:recommend:%s", user.getId());
        Object value = opsForValue.get(key);
        if (value != null) {
            return (List<User>) value;
        }
        //缓存中没有就查数据库
        Page<User> userPage = new Page<>(pageNum, pageSize);
        Page<User> page = this.page(userPage);
        List<User> records = page.getRecords();
        List<User> userList = records.stream().map(this::getSafetyUser).collect(Collectors.toList());
        //添加到缓存
        try {
            opsForValue.set(key, userList, 30, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("添加缓存失败！", e);
        }
        return userList;
    }

    /**
     * 更新用户信息
     * @param user 用户
     * @return 0 更新失败 1 更新成功
     */
    @Override
    public int updateUser(User user, HttpServletRequest request) {
        //1.判断参数
        if (user == null || request == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = this.getCurrentUser(request);
        //2.如果当前登录用户不是管理员或者当前登录用户和待修改用户不一致
        if (!this.isAdmin(loginUser) && !Objects.equals(user.getId(), loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        //3.更新用户
        boolean res = this.updateById(user);
        return res ? 1 : 0;
    }

    /**
     * 根据标签搜索用户——内存查询
     * @param tagNameList 标签集合
     * @return 拥有所有标签的用户
     */
    @Override
    public List<User> searchUserByTags_Memory(List<String> tagNameList) {
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //全量查询获取所有用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> allUsers = this.list(queryWrapper);
        Gson gson = new Gson();
        //依次判断每个用户是否符合条件
        List<User> userList = allUsers.stream().filter(user -> {
            String tags = user.getTags();
            if (StringUtils.isBlank(tags)) {
                return false;
            }
            //将tags json字符串转为对象
            Set userTagNameList = gson.fromJson(user.getTags(), new TypeToken<Set>() {
            }.getType());
            for (String s : tagNameList) {
                if (!userTagNameList.contains(s)) {
                    return false;
                }
            }
            return true;
        }).collect(Collectors.toList());
        return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
    }

    /**
     * 根据标签搜索用户——SQL查询
     * @param tagNameList 标签集合
     * @return 拥有所有标签的用户
     */
    @Override
    public List<User> searchUserByTags_SQL(List<String> tagNameList) {
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        /*if (tagNameList==null||tagNameList.size()==0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }*/
        //构建查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        for (String s : tagNameList) {
            queryWrapper.like("tags", s);
        }
        List<User> userList = this.list(queryWrapper);
        //返回脱敏后的用户集合
        return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
    }


    //用户中心
    @Override
    public User getCurrentUser(HttpServletRequest request) {
        //1.从登录态中获取当前登录用户
        Object userAttribute = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userAttribute;
        if (user == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN, "未登录");
        }
        //2.根据id查找当前用户的最新信息并返回
        User currentUser = this.getById(user.getId());
        return this.getSafetyUser(currentUser);
    }

    /**
     * 判断是否为管理员
     * @param request 请求
     * @return 是否为管理员
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        if (request == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = this.getCurrentUser(request);
        return user != null && user.getUserRole() == ADMIN_ROLE;
    }

    @Override
    public boolean isAdmin(User user) {
        if (user == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        return user.getUserRole() == ADMIN_ROLE;
    }

    @Override
    public BaseResponse<Long> userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) {
        //1.校验用户的账户、密码、校验密码是否符合要求
        //判断是否存在空串
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        //账户长度不小于4
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号长度过小");
        }
        //星球编号长度不大于5
        if (planetCode.length() > 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号过长");
        }
        //密码长度不小于8
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度过小");
        }
        //账户不包含特殊字符
        String validPattern = "[ _`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]|\n|\r|\t";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {//如果匹配到特殊字符
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号包含特殊字符");
        }
        //密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次密码不一致");
        }
        //账户不能重复（涉及到查询数据库，所以放到最后）
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        long count = this.count(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在");
        }
        //星球编号不能重复
        queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("planetCode", planetCode);
        count = this.count(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号已存在");
        }
        //2.对密码进行加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        //3.插入到数据库
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        boolean saveResult = this.save(user);
        if (!saveResult) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "插入用户到数据库失败");
        }
        return ResultUtil.ok(user.getId());
    }

    @Override
    public BaseResponse<User> userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        //1.校验用户账户和密码是否合法
        //判断是否存在空串
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        //账户长度不小于4
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号长度过小");
        }
        //密码长度不小于8
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度过小");
        }
        //账户不包含特殊字符
        String validPattern = "[ _`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]|\n|\r|\t";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {//如果匹配到特殊字符
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号包含特殊字符");
        }
        //2.校验密码是否输入正确
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount).eq("userPassword", encryptPassword);
        User user = this.getOne(queryWrapper);
        if (user == null) {
            log.info("user login failed, userAccount Cannot match userPassword");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号不存在");
        }
        //3.用户信息脱敏，隐藏敏感信息，防止数据库中的字段泄露
        User safetyUser = getSafetyUser(user);
        //4.记录用户的登录态
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
        //5.返回脱敏后的用户信息
        return ResultUtil.ok(safetyUser);
    }

    @Override
    public BaseResponse userLogout(HttpServletRequest request) {
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return ResultUtil.ok(null);
    }

    @Override
    public User getSafetyUser(User originUser) {
        if (originUser == null) {
            return null;
        }
        User safetyUser = new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUsername(originUser.getUsername());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setUserStatus(originUser.getUserStatus());
        safetyUser.setPlanetCode(originUser.getPlanetCode());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setCreateTime(originUser.getCreateTime());
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setTags(originUser.getTags());
        safetyUser.setProfile(originUser.getProfile());
        return safetyUser;
    }

    @Override
    public List<UserVO> matchUsers(Integer num, User currentUser) {
        //获取当前用户的标签
        String tags = currentUser.getTags();
        Gson gson = new Gson();
        List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {
        }.getType());
        //查询所有用户，只获取需要的数据
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(User::getId, User::getTags);
        List<User> allUsers = this.list(queryWrapper);
        //保存 下标-->相似度(编辑距离)
        List<Pair<Integer, Integer>> indexToDistance = new ArrayList<>();
        Long userId = currentUser.getId();
        for (int i = 0; i < allUsers.size(); i++) {
            User user = allUsers.get(i);
            //剔除自己、过滤掉标签为空的用户
            if (user.getId().equals(userId) || StringUtils.isBlank(user.getTags())) {
                continue;
            }
            //获取该用户的标签
            List<String> tempTags = gson.fromJson(user.getTags(), new TypeToken<List<String>>() {
            }.getType());
            //计算编辑距离
            int minDistance = AlgorithmUtils.minDistance(tagList, tempTags);
            //保存
            indexToDistance.add(new Pair<>(i, minDistance));
        }
        //获取Top N的用户id列表
        List<Long> idList = indexToDistance.stream().sorted((a, b) -> a.getValue() - b.getValue()).limit(num).map(Pair::getKey).map(item -> allUsers.get(item).getId()).collect(Collectors.toList());
        //根据id列表获取用户列表
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(User::getId, idList);
        //之前只获取了需要的数据，所以还要重新查询用户数据，但是查询后用户的顺序乱了
        List<User> userList = this.list(queryWrapper);
        //将userList按id进行分组
        Map<Long, List<User>> idToUser = userList.stream().collect(Collectors.groupingBy(User::getId));
        ArrayList<UserVO> finalList = new ArrayList<>();
        idList.stream().forEach(item -> {
            //id肯定是唯一的，所以value一定只有一个元素
            User user = idToUser.get(item).get(0);
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user, userVO);
            finalList.add(userVO);
        });
        return finalList;
    }
}




